/*
 *  Copyright 2004 Blandware (http://www.blandware.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.blandware.atleap.webapp.taglib.core.html;

import com.blandware.atleap.common.Constants;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts.taglib.TagUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspTagException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

/**
 * <p>Renders an HTML IFRAME element and loads content to it.
 * Content to be loaded is body of this tag.
 * </p>
 * <p>
 * There are two methods to load content. Method to be used is specified with
 * <b>loadMethod</b> attribute.
 * </p>
 * <p>
 * First method uses JavaScript. Here, content will be placed to invisible element
 * (currently DIV) that will be loaded to IFRAME on its load. To use this method,
 * you must include script files /scripts/core/klayers.js and
 * /scripts/core/global.jsp somewhere in page. <b>src</b> attribute (if specified)
 * will point to page that will be used as 'blank' page: it's content
 * (everything inside BODY element) will be replaced with loaded content.
 * <b>var</b> attribute is ignored.
 * </p>
 * <p>
 * Second method uses servlet to load content. Content is put to variable with
 * name <b>var</b> and then loaded by special servlet. Here, <b>src</b>
 * is ignored.
 * </p>
 * <p>
 * Allowed attributes are:
 * <ul>
 * <li>
 * <b>loadMethod</b> - load method to use. This can be 'javascript' or 'servlet',
 * default is 'javascript'
 * </li>
 * <li>
 * <b>var</b> - name of session scope variable that will be used to store
 * tag content. Used only when <code>loadMethod="servlet"</code>. If not
 * specified, this will be calculated automatically.
 * </li>
 * <li>
 * <b>src</b> - location of page that will be used as 'blank' page. Used only
 * when <code>loadMethod="javascript"</code>. If not specified, link to default
 * blank page is used.
 * </li>
 * <li>
 * <b>contentType</b> - content type of content, this can be 'text/plain' or
 * 'text/html'. Default is 'text/html'. Used only when <code>loadMethod="servlet"</code>.
 * </li>
 * <li>
 * <b>frameId</b> - ID of generated IFRAME element. If not specified, unique ID
 * is generated automatically.
 * </li>
 * <li>
 * <b>frameName</b> - name of generated IFRAME element. If not specified, this
 * is same as <b>frameId</b>.
 * </li>
 * <li>
 * <b>frameBorder</b> - frameBorder attribute value for generated IFRAME element
 * </li>
 * <li>
 * <b>marginWidth</b> - marginWidth attribute value for generated IFRAME element
 * </li>
 * <li>
 * <b>marginHeight</b> - marginHeight attribute value for generated IFRAME element
 * </li>
 * <li>
 * <b>scrolling</b> - scrolling attribute value for generated IFRAME element
 * </li>
 * <li>
 * <b>align</b> - align attribute value for generated IFRAME element
 * </li>
 * <li>
 * <b>width</b> - width attribute value for generated IFRAME element
 * </li>
 * <li>
 * <b>height</b> - height attribute value for generated IFRAME element
 * </li>
 * </ul>
 * </p>
 * <p><a href="IFrameTag.java.html"><i>View Source</i></a></p>
 *
 * @author Sergey Zubtsovskiy <a href="mailto:sergey.zubtsovskiy@blandware.com">&lt;sergey.zubtsovskiy@blandware.com&gt;</a>
 * @version $Revision: 1.14 $ $Date: 2006/08/03 10:08:23 $
 * @jsp.tag name="iframe"
 * body-content="scriptless"
 */
public class IFrameTag extends SimpleTagSupport {

	protected transient final Log log = LogFactory.getLog(IFrameTag.class);

	/**
	 * Page context key under which ordinal number of this tag is saved
	 */
	protected static final String NUMBER_KEY = "com.blandware.atleap.taglib.IFRAME_TAG_NUMBER";

	/**
	 * Prefix for default variable to store content if loadMethod="servlet" is used. Variable will be
	 * complemented with frame ID.
	 */
	protected static final String DEFAULT_VAR_PREFIX = "com.blandware.atleap.taglib.IFRAME_CONTENT_VAR_";

	/**
	 * Javascript load method
	 */
	protected static final String LOAD_METHOD_JS = "javascript";

	/**
	 * Servlet load method
	 */
	protected static final String LOAD_METHOD_SERVLET = "servlet";


	/**
	 * Load method. Can be "javascript" or "servlet"
	 */
	protected String loadMethod = LOAD_METHOD_JS;

	/**
	 * Variable to save content in session if loadMethod="servlet" is used
	 */
	protected String var;

	/**
	 * Type of content. Can be either <code>text/plain</code> or </code>text/html</code>.
	 * If loadMethod="javascript" is used, this attribute is ignored
	 */
	protected String contentType = Constants.MIME_TYPE_HTML;

	/**
	 * ID of this frame element. If not specified, it will be
	 * '__iframe__N' where N is an ordinal number of this tag on page.
	 */
	protected String frameId;

	/**
	 * Name of this iframe element
	 */
	protected String frameName;

	/**
	 * Frame border
	 */
	protected Integer frameBorder;

	/**
	 * Source of frame. If not specified, blank.html from the root directory will be used.
	 * Note, that all content between &lt;body&gt;&lt;/body&gt; tags will be replaced by
	 * body content of this tag, even if it is an empty string
	 */
	protected String src;

	/**
	 * Margin width in pixels
	 */
	protected Integer marginWidth;

	/**
	 * Margin height in pixels
	 */
	protected Integer marginHeight;

	/**
	 * Whether or not srollbar must be shown in frame
	 */
	protected String scrolling;

	/**
	 * Align of this frame element
	 */
	protected String align;

	/**
	 * Height of this frame element
	 */
	protected String height;

	/**
	 * Width of this frame element
	 */
	protected String width;

	/**
	 * Ordinal number of this tag on page
	 */
	protected Integer tagNumber;


	/**
     * Returns load method
     *
	 * @return load method
     * @see #loadMethod
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Load method. Can be 'javascript' or 'servlet'"
	 */
	public String getLoadMethod() {
		return loadMethod;
	}

    /**
     * Sets load method
     *
     * @param loadMethod load method to set
     * @see #loadMethod
     */
	public void setLoadMethod(String loadMethod) {
		this.loadMethod = loadMethod;
	}

	/**
     * Returns name of variable that will accept loaded content if load method
     * is 'servlet'
     *
	 * @return variable name
     * @see #var
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Variable to save content in session if loadMethod='servlet' is used"
	 */
	public String getVar() {
		return var;
	}

    /**
     * Sets name of variable that will accept loaded content if load method
     * is 'servlet'
     *
     * @param var variable name to set
     * @see #var
     */
	public void setVar(String var) {
		this.var = var;
	}

	/**
     * Returns content-type of content
     *
	 * @return content-type of content
     * @see #contentType
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Type of content. Usable only if loadMethod='servlet' is used, otherwise is ignored"
	 */
	public String getContentType() {
		return contentType;
	}

    /**
     * Sets content-type of content
     *
     * @param contentType content-type of content to set
     * @see #contentType
     */
	public void setContentType(String contentType) {
		this.contentType = contentType;
	}

	/**
     * Returns IFrame element ID
     *
	 * @return IFrame element ID
     * @see #frameId
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="ID of this frame element."
	 */
	public String getFrameId() {
		return frameId;
	}

    /**
     * Sets IFrame element ID
     *
     * @param frameId IFrame element ID to set
     * @see #frameId
     */
	public void setFrameId(String frameId) {
		this.frameId = frameId;
	}

	/**
     * Returns IFrame element name
     *
	 * @return IFrame element name
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Name of this frame element."
	 */
	public String getFrameName() {
		return frameName;
	}

    /**
     * Sets IFrame element name
     *
     * @param frameName IFrame element name to set
     */
	public void setFrameName(String frameName) {
		this.frameName = frameName;
	}

	/**
     * Returns IFrame element border
     *
	 * @return IFrame element border
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.Integer"
	 * description="Frame border"
	 */
	public Integer getFrameBorder() {
		return frameBorder;
	}

    /**
     * Sets IFrame element border
     *
     * @param frameBorder
     */
	public void setFrameBorder(Integer frameBorder) {
		this.frameBorder = frameBorder;
	}

	/**
     * Returns frame source
     *
	 * @return frame source
     * @see #src
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Source for this frame"
	 */
	public String getSrc() {
		return src;
	}

    /**
     * Sets frame source
     *
     * @param src frame source to set
     * @see #src
     */
	public void setSrc(String src) {
		this.src = src;
	}

	/**
     * Returns margin width in pixels
     *
	 * @return margin width
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.Integer"
	 * description="Margin width"
	 */
	public Integer getMarginWidth() {
		return marginWidth;
	}

    /**
     * Sets margin width in pixels
     *
     * @param marginWidth margin width to set
     */
	public void setMarginWidth(Integer marginWidth) {
		this.marginWidth = marginWidth;
	}

	/**
     * Returns margin height in pixels
     *
	 * @return margin height
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.Integer"
	 * description="Margin height"
	 */
	public Integer getMarginHeight() {
		return marginHeight;
	}

    /**
     * Sets margin height in pixels
     *
     * @param marginHeight margin height to set
     */
	public void setMarginHeight(Integer marginHeight) {
		this.marginHeight = marginHeight;
	}

	/**
     * Returns whether or not srollbar must be shown in frame
     *
	 * @return whether srollbar must be shown in frame
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Whether or not scrollbar must be shown"
	 */
	public String getScrolling() {
		return scrolling;
	}

    /**
     * Sets whether or not srollbar must be shown in frame
     *
     * @param scrolling whether srollbar must be shown in frame
     */
	public void setScrolling(String scrolling) {
		this.scrolling = scrolling;
	}

	/**
     * Returns align of IFrame element
     *
	 * @return align
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Align of this frame element"
	 */
	public String getAlign() {
		return align;
	}

    /**
     * Sets align of IFrame element
     *
     * @param align align to set
     */
	public void setAlign(String align) {
		this.align = align;
	}

	/**
     * Returns height of IFrame element
     *
	 * @return height
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Height of this frame element"
	 */
	public String getHeight() {
		return height;
	}

    /**
     * Sets height of IFrame element
     *
     * @param height height to set
     */
	public void setHeight(String height) {
		this.height = height;
	}

	/**
     * Returns width of IFrame element
     *
	 * @return width
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Width of this frame element"
	 */
	public String getWidth() {
		return width;
	}

    /**
     * Sets width of IFrame element
     *
     * @param width width to set
     */
	public void setWidth(String width) {
		this.width = width;
	}

    /**
     * Processes the tag
     *
     * @throws JspException
     * @throws IOException
     */
	public void doTag() throws JspException, IOException {

		PageContext pageContext = (PageContext) getJspContext();

		if ( loadMethod == null || loadMethod.length() == 0 ) {
			loadMethod = LOAD_METHOD_JS;
		}

		if ( contentType == null || contentType.length() == 0 ) {
			contentType = Constants.MIME_TYPE_HTML;
		}

		if ( !LOAD_METHOD_JS.equalsIgnoreCase(loadMethod) && !LOAD_METHOD_SERVLET.equalsIgnoreCase(loadMethod) ) {
			JspTagException e = new JspTagException("Unrecognized load method: " + loadMethod);
			throw e;
		}

		if ( !Constants.MIME_TYPE_PLAIN.equals(contentType) && !Constants.MIME_TYPE_HTML.equals(contentType) ) {
			JspTagException e = new JspTagException("Unsupported content type: " + contentType);
			throw e;
		}

		// get ordinal number of this tag
		tagNumber = (Integer) pageContext.getAttribute(NUMBER_KEY);

		if ( tagNumber == null ) {
			tagNumber = new Integer(0);
		} else {
			tagNumber = new Integer(tagNumber.intValue() + 1);
		}

		pageContext.setAttribute(NUMBER_KEY, tagNumber);

		String content = new String();
		StringWriter sw = new StringWriter();
		JspFragment body = getJspBody();
		if ( body != null ) {
			body.invoke(sw);
			content = sw.toString();
		}

		if ( frameId == null ) {
			frameId = "__iframe__" + tagNumber;
		}

		if ( frameName == null ) {
			frameName = frameId;
		}

		TagUtils tagUtils = TagUtils.getInstance();
		if ( LOAD_METHOD_JS.equals(loadMethod) ) {
            // make an undisplayable DIV that will contain content for load
			String contentDivId = frameId + "__content__";
			String encodedContent = new String();
			try {
                // URL encoder is used here to encode content which is not
                // actually URL because it's quite enough for our purposes and
                // rather convenient: decoding is pretty simple
				encodedContent = URLEncoder.encode(content, Constants.DEFAULT_ENCODING).replaceAll("\\+", "%20");
			} catch ( UnsupportedEncodingException e ) {
				if ( log.isErrorEnabled() ) {
					sw = new StringWriter();
					e.printStackTrace(new PrintWriter(sw));
					log.error(sw.toString());
				}
			}
			StringBuffer contentDiv = new StringBuffer("<div id=\"").append(contentDivId).append("\" style=\"display: none;\">\n").append(encodedContent).append("</div>\n");
			if ( src == null ) {
				String ctxPath = ((HttpServletRequest) pageContext.getRequest()).getContextPath();
				src = ctxPath + "/blank.html";
			}
			StringBuffer onload = new StringBuffer("loadIFrameContent('")
			        .append(frameId).append("', '").append(contentDivId).append("');");

			StringBuffer iframe = new StringBuffer("<iframe id=\"")
			        .append(frameId).append("\" name=\"").append(frameName)
			        .append("\" src=\"").append(src).append("\" ")
			        .append(getAttributesStringBuffer()).append("onload=\"")
			        .append(onload).append("\" ").append("></iframe>");

			tagUtils.write(pageContext, contentDiv.append(iframe).toString());
		} else {
			// loading using servlet
			if ( var == null || var.length() == 0 ) {
				var = DEFAULT_VAR_PREFIX + frameId;
			}

			pageContext.setAttribute(var, content, PageContext.SESSION_SCOPE);
			String contextPath = ((HttpServletRequest) pageContext.getRequest()).getContextPath();
			src = contextPath + "/servlet/iframeContentLoader/?var=" + var + "&type=" + contentType;
			String iframe = "<iframe id=\"" + frameId + "\" name=\"" + frameName + "\" src=\"" + src + "\" " +
			        getAttributesStringBuffer() + "></iframe>";
			tagUtils.write(pageContext, iframe);
		}

	}

    /**
     * Returns a StringBuffer with attributes set to their values
     *
     * @return StringBuffer with attributes set to their values
     */
	protected StringBuffer getAttributesStringBuffer() {
		StringBuffer result = new StringBuffer();

		if ( frameBorder != null ) {
			result.append("frameborder=\"" + frameBorder + "\" ");
		}

		if ( marginWidth != null ) {
			result.append("marginwidth=\"" + marginWidth + "\" ");
		}

		if ( marginHeight != null ) {
			result.append("marginheight=\"" + marginHeight + "\" ");
		}

		if ( scrolling != null ) {
			result.append("scrolling=\"" + scrolling + "\" ");
		}

		if ( align != null ) {
			result.append("align=\"" + align + "\" ");
		}

		if ( width != null ) {
			result.append("width=\"" + width + "\" ");
		}

		if ( height != null ) {
			result.append("height=\"" + height + "\" ");
		}

		return result;
	}

}
